Dot Product of Block Matrices

One way is to turn them into ordinary matrices, take the dot product, and then ArrayReshape them into the form you want.

Af = ArrayFlatten[A];
Bf = Flatten[B];

ArrayReshape[Af.Bf, {3, 1, 2}] // MatrixForm

enter image description here

Just to check:

ArrayReshape[Af.Bf, {3, 1, 2}] == dotdot[A, B]

(* True *)

You could use TensorContract instead:

r1 = dotdot[A, B];
r2 = TensorTranspose[
    TensorContract[TensorProduct[A, B], {{2, 5}, {4, 7}}],
    {1, 3, 2}
];

r1 === r2

True

Comparison

One advantage of the TensorContract approach is that if A and B are sparse arrays, then the result is also a sparse array. This is not true for the other answers. For example:

A=SparseArray @ RandomChoice[{.9,.1}->{0,1}, {5, 6, 7, 8}];
B=SparseArray @ RandomChoice[{.9,.1}->{0,1}, {6, 9, 8}];

r1 = dotdot[A, B];
r2 = TensorTranspose[
    TensorContract[TensorProduct[A, B], {{2, 5}, {4, 7}}],
    {1, 3, 2}
];

Normal[r1] === Normal[r2]
Head /@ {r1, r2}

True

{List, SparseArray}

I didn't include @aardvark2012's answer because ArrayReshape apparently doesn't work with sparse arrays anyway